module Banker::guessing_game {
    use std::vector;
    use std::hash;
    use aptos_framework::account;
    use std::signer;
    use aptos_framework::event;
    use std::string::{String, Self};
    use aptos_std::string_utils;

    // Errors
    const E_NOT_INITIALIZED: u64 = 0x10;
    const PLAYER_ALREAY_EXISTS: u64 = 0x11;
    const PLAYER_NOT_EXISTS: u64 = 0x12;
    const PLAYER_FULL_ERROR: u64 = 0x13;
    const PLAYER_OPT_ERROR: u64 = 0x14;
    const PLAYER_OPPONENT_NOT_EXISTS: u64 = 0x15;
    const PLAYER_OPERAT_ERROR: u64 = 0x16;
    const PLAYER_HSAH_ERROR: u64 = 0x17;

    // option opt
    const OPT_SCISSOR:u8 = 0;
    const OPT_ROCK:u8 = 1;
    const OPT_PAPER:u8 = 2;
    const OPT_INIT:u8 = 9;

    // option status
    const STATUS_INIT:u8 = 0;
    const STATUS_HASH:u8 = 1;
    const STATUS_PROOF:u8 = 2;

    // option WIN
    const RTN_WIN:u8 = 0;
    const RTN_DRAW:u8 = 1;
    const RTN_LOSE:u8 = 2;

    struct RockList has key {
        nick_names: vector<String>,
        player1: address,
        player2: address,
        rock_opt1: Rock,
        rock_opt2: Rock,
        add_event: event::EventHandle<Player>,
        counter: u64,
        upd_event: event::EventHandle<Rock>,
        is_finished: bool,
    } 

    struct Rock has key, store, copy, drop {
        addr: address,
        opt: u8, 
        hash: String,
        round: u8,
        status: u8
    }

    struct Player has store, drop, copy {
        nick_name : String,
        addr: address
    }

    fun init_module(sender: &signer) {
        let rock_list = RockList {
            player1: @0xCAFE,
            player2: @0xCAFE,
            nick_names: vector::empty<String>(),
            rock_opt1: new_empty_rock(@0xCAFE),
            rock_opt2: new_empty_rock(@0xCAFE),
            add_event: account::new_event_handle<Player>(sender),
            upd_event: account::new_event_handle<Rock>(sender),
            counter: 0,
            is_finished: false,
        };
        // move the RockList resource under the signer account
        move_to(sender, rock_list);
    }

    fun new_empty_rock(addr: address) :Rock {
        Rock {
            addr,
            opt: OPT_INIT, 
            hash: string::utf8(b"0x"),
            round: 0,
            status: STATUS_INIT
        }
    }

    public entry fun join_game(account: &signer, nick_name: String) acquires RockList {
        // gets the signer address
        let signer_address = signer::address_of(account);
        // assert signer has created a list
        assert!(exists<RockList>(@Banker), E_NOT_INITIALIZED);
        // gets the UserList resource
        let rock_list = borrow_global_mut<RockList>(@Banker);
        // check player count < 2 
        assert!(rock_list.counter < 2, PLAYER_FULL_ERROR);
        if(rock_list.counter == 0) {
            rock_list.player1 = signer_address;
            rock_list.rock_opt1.addr = signer_address;
            
        } else {
            rock_list.rock_opt2.addr = signer_address;
            rock_list.player2 = signer_address; 
        };
        rock_list.counter = rock_list.counter + 1;

        let new_player = Player{
            nick_name,
            addr: signer_address
        };

        vector::push_back(&mut rock_list.nick_names, nick_name);

        // fires a new player created event
        event::emit_event<Player>(
            &mut rock_list.add_event,
            new_player,
        );

        move_to(account, rock_list.rock_opt1);
    }

    public entry fun submit(account: &signer, hash: String) acquires RockList {
        // gets the signer address
        let signer_address = signer::address_of(account);
        // assert signer has created a list
        assert!(exists<RockList>(@Banker), E_NOT_INITIALIZED);
        // gets the UserList resource
        let rock_list = borrow_global_mut<RockList>(@Banker);
        // check player count < 2 
        assert!(rock_list.counter == 2, PLAYER_OPPONENT_NOT_EXISTS);
        // eheck game's player
        assert!(signer_address == rock_list.player1 || signer_address == rock_list.player2, PLAYER_ALREAY_EXISTS);

        let (rock1, rock2) = (&mut rock_list.rock_opt1, rock_list.rock_opt2);
        if(signer_address == rock_list.player2) {
            (rock1, rock2) = (&mut rock_list.rock_opt2, rock_list.rock_opt1);
        };

        assert!(rock1.status == STATUS_INIT, PLAYER_OPERAT_ERROR);
        assert!(rock1.round <= rock2.round, PLAYER_OPERAT_ERROR);
        rock1.status = STATUS_HASH;
        rock1.round = rock1.round + 1;
        rock1.hash = hash;

        // fires a update event
        event::emit_event<Rock>(
            &mut rock_list.upd_event,
            *rock1,
        );
    }

    

    public entry fun proof(account: &signer, salt: String, opt: u8) acquires RockList {
        // gets the signer address
        let signer_address = signer::address_of(account);
        // assert signer has created a list
        assert!(exists<RockList>(@Banker), E_NOT_INITIALIZED);
        // gets the UserList resource
        let rock_list = borrow_global_mut<RockList>(@Banker);
        // check player count < 2 
        assert!(rock_list.counter == 2, PLAYER_OPPONENT_NOT_EXISTS);
        // eheck game's player
        assert!(signer_address == rock_list.player1 || signer_address == rock_list.player2, PLAYER_ALREAY_EXISTS);

        let (rock1, rock2) = (&mut rock_list.rock_opt1, rock_list.rock_opt2);
        if(signer_address == rock_list.player2) {
            (rock1, rock2) = (&mut rock_list.rock_opt2, rock_list.rock_opt1);
        };

        assert!(rock1.status == STATUS_HASH, PLAYER_OPERAT_ERROR);
        assert!(rock1.round == rock2.round, PLAYER_OPERAT_ERROR);
        rock1.status = STATUS_INIT;
        rock1.opt = opt;

        let proof = hash_opt(salt, opt);
        assert!(proof == rock1.hash, PLAYER_HSAH_ERROR);
        if(rock1.status == rock2.status) {
            let result = check(rock1.opt, rock2.opt);
            if(result != RTN_DRAW) {
                rock_list.is_finished = true;
            };
        };

        // fires a update event
        event::emit_event<Rock>(
            &mut rock_list.upd_event,
            *rock1,
        );
    }

    public entry fun reset(account: &signer) acquires RockList {
        // gets the signer address
        let signer_address = signer::address_of(account);
        // assert signer has created a list
        assert!(exists<RockList>(signer_address), E_NOT_INITIALIZED);

        // gets the UserList resource
        let rock_list = borrow_global_mut<RockList>(signer_address);

        let player1 = rock_list.player1;
        let player2 = rock_list.player2;
        rock_list.rock_opt1 = new_empty_rock(player1);
        rock_list.rock_opt2 = new_empty_rock(player2);
        rock_list.is_finished = false;

    }

    #[view]
    public fun check(opt1: u8, opt2: u8): u8 {
        assert!(opt1 <= 2, PLAYER_OPT_ERROR);
        assert!(opt2 <= 2, PLAYER_OPT_ERROR);
        if(opt1 > opt2) {
            if(opt1 - opt2 == 1) {
                RTN_WIN
            } else {
                RTN_LOSE
            }
            
        } else if (opt1 < opt2) {
            if(opt2 - opt1 == 1) {
                RTN_LOSE
            } else {
                RTN_WIN
            }
            
        } else {
            RTN_DRAW
        }
        // default draw
        
    }

    #[view]
    public fun check2(): u8 acquires  RockList {
        // assert signer has created a list
        assert!(exists<RockList>(@Banker), E_NOT_INITIALIZED);
        // gets the UserList resource
        let rock_list = borrow_global_mut<RockList>(@Banker);
        check(rock_list.rock_opt1.opt, rock_list.rock_opt2.opt)
        
    }

    #[view]
    fun hash_opt(salt: String, opt: u8): String {
        //vector::push_back(&mut salt, opt);
        let data = *string::bytes(&salt);
        vector::push_back(&mut data, opt);
        string_utils::to_string(&(hash::sha2_256(data)))
    }

    #[view]
    fun get_winner(): String acquires  RockList {
        // assert signer has created a list
        assert!(exists<RockList>(@Banker), E_NOT_INITIALIZED);
        // gets the UserList resource
        let rock_list = borrow_global_mut<RockList>(@Banker);
        let result = check(rock_list.rock_opt1.opt, rock_list.rock_opt2.opt);
        if(result == RTN_WIN) {
            *vector::borrow<String>(&rock_list.nick_names, 0)
        } else if (result == RTN_LOSE) {
            *vector::borrow<String>(&rock_list.nick_names, 1)
        } else {
            string::utf8(b"None")
        }
        
        
    }

    #[view]
    fun get_rocklist(): (Rock, Rock, address, address, vector<String>, u64, bool) acquires  RockList {
        // assert signer has created a list
        assert!(exists<RockList>(@Banker), E_NOT_INITIALIZED);
        // gets the UserList resource
        let rock_list = borrow_global_mut<RockList>(@Banker);
        (rock_list.rock_opt1, rock_list.rock_opt2, rock_list.player1, rock_list.player2, rock_list.nick_names, rock_list.counter, rock_list.is_finished)
    }

}
//0x8a45bb18b060d866800dc172d43ab0af800895229ed077e9bd7b80a3081b9289